package nl.liacs.swi.tower;

/*
 * Game class.
 * 
 * This class draws the game screen and updates the game logic. The game 
 * class contains these methods:
 * 
 *  + Game()				constructor, adds initial towers at random positions
 *  + Update()				updates the game logic
 *  + Draw()				draws the game world
 *  
 *  - addAnts()				adds ants to the game according to the tick interval
 *  - updateAnts()			for each ant A in the list of ants L1, update the logic.
 *  - updateTowers()		for each tower T in the list of towers L2, update the logic.
 *  - checkCollision()		if, and only if, these conditions are met:
 *  							- ant in range of tower 
 *  							- tower can shoot
 *  						then a tower has hit an ant. This method not only verifies,
 *  						but also does the corresponding logic.
 *  - gameDelay()			a delay for the game loop. Mess with this to speed up or slow down
 *  						overal process.
 *  - inRange()				checks if object (x2,y2) is in range of (x1,x2)
 *  - checkFinishedRound()  starts a new round, when the conditions (ants dead or finished) are met.
 *  				
 *  
 */
import java.awt.Canvas;
import java.awt.Choice;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Label;
import java.awt.Point;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferStrategy;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public class Game extends Canvas{

	private Game game;
	
	// 1: manue 2: random 3: aoc
	public int mode = 1;
	private boolean running = true;
	private boolean pause = true;
	
	private boolean initialized = false;
	private List<Ant> ants = new ArrayList<Ant>();
	private List<Tower> towers = new ArrayList<Tower>();
	private int MAX_ANTS = 50;
	private int MAX_TOWERS = 6;
	private int nr_strong_towers = 0;
	private int nr_normal_towers = 6;
	private int nr_long_towers = 0;
	private int gameDelay = 30;

	private int ANT_RESPAWN_TIME = 10;
	private int tick = 0;
	private int round = 0;
	private int antsLive = 10;
	private float alpha = 0.8f;
	/** The stragey that allows us to use accelerate page flipping */
	private BufferStrategy strategy;

	private int Z = 0; // total kills

	private int roundKills = 0;
	private int totalKills = 0;
	private int totalFinished = 0;
	
	private int ACO_iteration = 1;

	private List<Integer> best_x = new ArrayList<Integer>(); // best solution, x
																// coordinates
																// of towers
	private List<Integer> best_y = new ArrayList<Integer>(); // best solution, y
																// coordinates
																// of towers

	/**
	 * Construct our game and set it running.
	 */
	public Game() {
		// create a frame to contain our game
		JFrame container = new JFrame("Tower Defense");
		// get hold the content of the frame and set up the resolution of the game
		JPanel panel = (JPanel) container.getContentPane();
		panel.setPreferredSize(new Dimension(800,600));
		panel.setLayout(null);
		// setup our canvas size and put it into the content of the frame
		setBounds(0,0,800,600);
		panel.add(this);
		
		// Tell AWT not to bother repainting our canvas since we're
		// going to do that our self in accelerated mode
		setIgnoreRepaint(true);
		// finally make the window visible 
		container.pack();
		container.setResizable(false);
		container.setVisible(true);
		container.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		// add a key input system (defined below) to our canvas
		addKeyListener(new KeyHandler());
		addMouseListener(new MouseHandler());
		
		// request the focus so key events come to us
		requestFocus();

		// create the buffering strategy which will allow AWT
		// to manage our accelerated graphics
		createBufferStrategy(2);
		strategy = getBufferStrategy();
		game = this;
	}
	
	
	public static void main(String argv[]) {
		Game game = new Game();
		game.start();
	}
	
	private void start(){
		game.running = true;
		GameLoop loop = new GameLoop();
		loop.run();
	}
	
	private void reset(){
		game.pause = true;
		towers.clear();
		ants.clear();
		this.round = 0;
		this.roundKills = 0;
		this.totalKills = 0;
		this.totalFinished = 0;
		this.initialized = false;
		ACO_initializePheromones();
		System.out.println("\n RESET ==============================");
	}
	
	class GameLoop extends Thread{
		@Override
		public void run(){
			while (running) {
				update();
				render();
			}
		}
	}
		
	
	// ------------------------------------------------------------------------
	// Game logic
	// ------------------------------------------------------------------------
	
	private void addTowers() {
		for (int i = 0; i < nr_normal_towers; i++) {
			Tower t = new Tower(alpha);
			towers.add(t);
		}
		for (int i = 0; i < nr_strong_towers; i++) {
			Tower t = new StrongTower(alpha);
			towers.add(t);
		}
		for (int i = 0; i < nr_long_towers; i++) {
			Tower t = new LongRangeTower(alpha);
			towers.add(t);
		}
	}
	
	void addSingleTower(Point point){
		if(mode == 1){
			Tower t = new Tower(alpha);
			t.x = (int) point.getX()-20;
			t.y = (int) point.getY()-20;
			towers.add(t);
		}
	}
	
	public void initialize(){
		if(mode != 1 && !initialized){
			if(mode == 3)
				this.ACO_initializePheromones();
			
			addTowers();
			for (int i = 0; i < towers.size(); i++) {
				// Choose next position for tower
					towers.get(i).newPosition(mode);
			}
			initialized = true;
		}
	}

	public void update() {
		if(!pause){
			this.initialize();
			this.ACO_updateTowers();
			addAnts();
			updateAnts();
			updateTowers();
			if (isRoundFinished())
				this.roundFinished();
		}
		gameDelay();
	}

	public void incKills() {
		this.totalKills++;
		this.roundKills++;
	}

	public void incFinished() {
		this.totalFinished++;
	}

	public void render() {
		Graphics2D g = (Graphics2D) strategy.getDrawGraphics();
		g.setColor(Color.white);
		g.fillRect(0,0,800,600);

		// draw map
		for (int y = 0; y < Map.HEIGHT; y++) {
			for (int x = 0; x < Map.WIDTH; x++) {
				
				g.setColor(Color.white);
				
				if (Map.getSingletonObject().map[y][x] == 1)
					g.setColor(Color.green);
				
				if (Map.getSingletonObject().map[y][x] == 2)
					g.setColor(Color.lightGray);

				g.fillRect(x * 40, y * 40, 40, 40);
			}
		}

		// draw ants
		g.setColor(Color.black);
		for (Ant ant : ants) {
			if (!(ant.isDead() || ant.isFinished()))
				g.fillOval(ant.getX(), ant.getY(), 8, 8);
		}

		// draw pheromone
		if(mode == 3){
		for (int y = 0; y < 10; y++) {
			for (int x = 0; x < 13; x++) {
				if (Map.getSingletonObject().pheromones[y][x] > 1) {
					g.setColor(Color.yellow);
					double val = 10 * Map.getSingletonObject().pheromones[y][x];
					if (val + 50 > 255)
						val = 250;
					// System.out.println(val);
					Color c = new Color((int) val, 0, 0);
					g.setColor(c);
					g.fillRect(x * 40, y * 40, 40, 40);
				}
			}
		}
		} 

		// draw towers
		this.drawTowers(g);

		// draw statstics
		g.setColor(Color.BLACK);
		int numPos = 150;
		int speed = 30-this.gameDelay;
		
		
		String modeString = (mode==1)?"manual":(mode==2)?"random":(mode==3)?"aco":"";
		
		int xpos = 444;
		int gap = 12;
		g.drawString("Paused", 10, xpos);g.drawString(": " + pause,numPos,xpos);xpos=xpos+gap;
		g.drawString("Mode", 10, xpos);g.drawString(": " + modeString,numPos,xpos);xpos=xpos+gap;
		g.drawString("Alpha", 10, xpos);g.drawString(": " + alpha,numPos,xpos);xpos=xpos+gap;
		g.drawString("Speed", 10, xpos);g.drawString(": " + speed,numPos,xpos);xpos=xpos+gap;
		g.drawString("Round", 10, xpos);g.drawString(": " + round,numPos,xpos);xpos=xpos+gap;
		g.drawString("# normal towers", 10, xpos);g.drawString(": " + this.nr_normal_towers,numPos,xpos);xpos=xpos+gap;
		g.drawString("# strong towers", 10, xpos);g.drawString(": " + this.nr_strong_towers,numPos,xpos);xpos=xpos+gap;
		g.drawString("# long range towers", 10, xpos);g.drawString(": " + this.nr_long_towers,numPos,xpos);xpos=xpos+gap;
		g.drawString("======================================", 10, xpos);xpos=xpos+gap;
		g.drawString("Ants Power", 10, xpos);g.drawString(": " + this.antsLive,numPos,xpos);xpos=xpos+gap;
		g.drawString("Ants killed", 10, xpos);g.drawString(": " + this.totalKills,numPos,xpos);xpos=xpos+gap;
		g.drawString("Ants finished", 10, xpos);g.drawString(": " + this.totalFinished,numPos,xpos);xpos=xpos+gap;
		
		

		g.dispose();
		strategy.show();
	}
	
	private void drawTowers(Graphics gc){
		for (Tower tower : towers) {
			// don't draw tower if dead
			if(tower.isDead())
				continue;
			if (!tower.isHitting())
				gc.setColor(tower.getColor());
			else
				gc.setColor(Color.red);

			gc.fillRect(tower.getX() + 10, tower.getY() + 10, 20, 15);
			// draw health bar
			int life = (int)(((double)tower.life/(double)tower.totalLife) * 20);
			gc.setColor(Color.orange);
			gc.fillRect(tower.getX() + 10, tower.getY() + 25,life, 5);
			gc.setColor(Color.red);
			gc.fillRect(tower.getX() + 10 + life, tower.getY() + 25,20-life, 5);
		}
	}

	private void addAnts() {
		// Add ants
		tick++;
		if (tick == ANT_RESPAWN_TIME) {
			if (ants.size() < MAX_ANTS) {
				Ant ant = new Ant(this, this.antsLive);
				ant.setX(20);
				ant.setY(60);
				ants.add(ant);

				tick = 0;
			}
		}
		if (tick > ANT_RESPAWN_TIME + 1)
			tick = 0;

	}

	private void updateAnts() {
		for (int i = 0; i < ants.size(); i++) {
			ants.get(i).update(towers);
		}
	}

	private void updateTowers() {
		for (int i = 0; i < towers.size(); i++) {
			towers.get(i).update(ants,towers);
		}
	}

	/**
	 * @return true if round is finished, false otherwise
	 */
	private boolean isRoundFinished() {
		if (ants.size() >= MAX_ANTS) {
			// if there is an ant whish is not dead or not finished return false
			for (Ant ant : ants) {
				if (!ant.isFinished() && !ant.isDead())
					return false;
			}
			return true;
		}
		return false;

	}

	/**
	 * called if round is finished
	 */
	private void roundFinished() {
		ants.clear();
		round++;
		if(mode!=1)
			this.ACO_finishedRound();
		System.out.print("\n Round " + round + " : kills = " + roundKills + " : total kills " + this.totalKills + " | ");
		roundKills = 0;
	}

	private void gameDelay() {
		try {
			Thread.sleep(gameDelay);
		} catch (InterruptedException ex) {

		}
	}

	public void speedDown() {
		this.gameDelay =  (this.gameDelay == 30)?30:gameDelay+2;
	}

	public void speedUp() {
		this.gameDelay = (this.gameDelay == 0)?0:gameDelay-2;
	}
	
	public void setPause(boolean pause){
		this.pause = pause;
	}

	// ------------------------------------------------------------------------
	// ACO logic
	// ------------------------------------------------------------------------

	private void ACO_initializePheromones() {
		for (int i = 0; i < Map.WIDTH; i++) {
			for (int j = 0; j < Map.HEIGHT; j++) {
				Map.pheromones[j][i] = Map.getPheromone_base_value();
			}
		}
	}

	private void ACO_updateTowers() {
		//for (int i = 0; i < towers.size(); i++) {
			// towers.get(i).update();
			// Choose next position for tower
			// towers.get(i).newRound();

		//}

	}

	private void ACO_finishedRound() {

		this.ACO_updatePheromones();

		this.ACO_clearRegistrated();

		for (int i = 0; i < towers.size(); i++) {

			// Choose next position for tower
			if(mode != 1)
				towers.get(i).newPosition(mode);
		}

		// --------------------------------------------------
		// Last step: is better route?

		// Count total kills
		int totalKills = 0;
		for (int i = 0; i < towers.size(); i++) {
			totalKills += towers.get(i).getKills();
		}

		//System.out.print("\nIteration " + ACO_iteration + " : kills = " + totalKills + " : total kills " + this.totalKills + " | ");

		// Is total counted kills more than total kills found in previous
		// rounds?
		if (totalKills > Z) {

			// Update list and #kills.
			Z = totalKills;

			best_x.clear();
			best_y.clear();

			System.out.print("  A better solution is found, towers at: ");
			for (int i = 0; i < towers.size(); i++) {
				int tmp_x = towers.get(i).getX() / 40;
				int tmp_y = towers.get(i).getY() / 40;

				best_x.add(tmp_x);
				best_y.add(tmp_y);

				System.out.print(" (" + tmp_x + "," + tmp_y + ") ");
			}
			// System.out.println();
		} else {
			// System.out.println("No better solution found.");
		}

		// Reset kill counter each iteration
		for (Tower t:towers) {
			t.clearKills();
			t.setHits(0);
		}
		
		totalKills = 0;
		ACO_iteration++;

	}

	private void ACO_intensifyPheromones() {
		double rho = 1;
		double Q = 1;
		double L = 0;

		for (int i = 0; i < towers.size(); i++) {
			L = (double) towers.get(i).getHits();
			;
			Map.pheromones[towers.get(i).getY() / 40][towers.get(i).getX() / 40] += Q
					* L; // 50; //(qval/(-L)) * rho;

			// System.out.println( Q + "*" + L + "=" + Q*L);
		}
	}

	private void ACO_evaporatePheromones() {
		double rho = 0.01;

		for (int y = 0; y < Map.HEIGHT; y++) {
			for (int x = 0; x < Map.WIDTH; x++) {
				Map.pheromones[y][x] *= (1.0 - rho);

				if (Map.pheromones[y][x] < 0.0) {
					Map.pheromones[y][x] = 0; // base_pherom;
				}
			}

		}
	}

	private void ACO_updatePheromones() {
		this.ACO_intensifyPheromones();
		this.ACO_evaporatePheromones();
	}

	private void ACO_clearRegistrated() {
		// Clear previously registered places
		for (int y = 0; y < Map.HEIGHT; y++) {
			for (int x = 0; x < Map.WIDTH; x++) {
				if (Map.getSingletonObject().map[y][x] == 0xFF)
					Map.getSingletonObject().map[y][x] = 0;
			}
		}

	}
	
	// 
	// Key Handler
	//
	class KeyHandler extends KeyAdapter{
		// Method for the key pressed
		public void keyPressed(KeyEvent e) {
			switch(e.getKeyChar()){
			case 's': // key -
				game.start();
				break;
			case 'r': // key -
				game.reset();
				break;
			case '-': // key -
				game.speedDown();
				break;
			case '+': // key +
				game.speedUp();
				break;
			case ' ': // key space
				game.pause = !pause;
				break;
			case 'a': // key a
				game.alpha = (game.alpha<1.0f)?game.alpha+0.1f:0.0f;
				break;
			case 'm': // key a
				game.mode = (game.mode<=2)?game.mode+1:1;
				break;
			case 'p': // key a
				game.antsLive = (game.antsLive<100)?game.antsLive+10:10;
				break;	
			case '1': // key 1
				game.nr_normal_towers = (game.nr_normal_towers<MAX_TOWERS)?game.nr_normal_towers+1:0;
				break;
			case '2': // key 1
				game.nr_strong_towers = (game.nr_strong_towers<MAX_TOWERS)?game.nr_strong_towers+1:0;
				break;
			case '3': // key 1
				game.nr_long_towers = (game.nr_long_towers<MAX_TOWERS)?game.nr_long_towers+1:0;
				break;
			}
		}
		
	}
	
	//
	// Mouse Handler
	//
	class MouseHandler implements MouseListener{

		@Override
		public void mouseClicked(MouseEvent e) {
			game.addSingleTower(e.getPoint());
		}

		@Override
		public void mouseEntered(MouseEvent arg0) {
		}

		@Override
		public void mouseExited(MouseEvent arg0) {
		}

		@Override
		public void mousePressed(MouseEvent arg0) {
		}

		@Override
		public void mouseReleased(MouseEvent arg0) {
		}
		
	}

}
